/*
 * test_framework_network_connection.h
 *
 *  Created on: 2014. 6. 30.
 *      Author: jjaehuny
 */

#ifndef TEST_FRAMEWORK_NETWORK_CONNECTION_H_
#define TEST_FRAMEWORK_NETWORK_CONNECTION_H_

#include <memory>
#include <string>
#include <vector>
#include <chrono>

#include <gtest/gtest.h>
#include <glog/logging.h>

#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
#include <google/protobuf/text_format.h>

#include <boost/bind.hpp>

#include <Framework/Network/Static.h>
#include <Framework/Network/Session.h>
#include <Framework/Network/Listener.h>
#include <Framework/Timer/Handle.h>

#include <Framework/Memory/Function_new_Pool.h>
#include <Framework/Memory/Lock_Function_new_Pool.h>

#include <GoogleProtocolBuffer/message/pingpong.pb.h>

class server : public ::Framework::Network::Session::Observer, public std::enable_shared_from_this<server>
{
public:
	server() {}
	~server() {}

	void Initialize() {}
	void Finalize() { LOG(INFO) << __PRETTY_FUNCTION__; session_ = NULL; }

	virtual void OnEvent(::Framework::Network::Session::Observer::E_EVENT event) {
		switch(event)
		{
		case ::Framework::Network::Session::Observer::E_EVENT::OPEN:
			OnOpen();
			break;
		case ::Framework::Network::Session::Observer::E_EVENT::CLOSE:
			OnClose();
			break;
		case ::Framework::Network::Session::Observer::E_EVENT::MESSAGE:
			OnMessage();
			break;
		}
	}

	std::shared_ptr<server> CAPTURE() { return shared_from_this(); }

	void set_session(std::shared_ptr<::Framework::Network::Session> session) {
		session_ = session;
	}

private:
	void OnOpen() {
		LOG(INFO) << __PRETTY_FUNCTION__;
	}

	void OnClose() {
		LOG(INFO) << __PRETTY_FUNCTION__;
		session_ = NULL;
	}

//	void OnConnectFail(boost::system::error_code & ec) {
//		LOG(INFO) << __PRETTY_FUNCTION__ << ec.value();
//	}

	void OnMessage() {

		std::shared_ptr<::Framework::Network::Packet> packet = session_->clsReadPacket_deque_.front();
		session_->clsReadPacket_deque_.pop_front();

		LOG(INFO) << __PRETTY_FUNCTION__ << "server read command : " << packet->get_command() << "]";

		if (1 == packet->get_command())
		{
			google::protobuf::io::ArrayInputStream is(packet->BodyBuffer(), packet->get_body_length_());
			Test::ping_syn syn;
			syn.ParseFromZeroCopyStream(&is);
			//int commands = syn.command();
			int commands = 0;
			int id = syn.id();

			LOG(INFO) << __PRETTY_FUNCTION__ << "[ server ping_syn id : " << id << "] [commands : " << commands << "]";
		}

		Test::pong_ack ack;
		ack.set_id(2);
		session_->Send(ack);
	}

private:
	std::shared_ptr<::Framework::Network::Session> session_;
};























class client : public ::Framework::Network::Session::Observer, public std::enable_shared_from_this<client>
{
public:
	client() { handle_ = ::Framework::Timer::create_handle_singlethread(); }
	~client() {}

	void Initialize() {}
	void Finalize() { LOG(INFO) << __PRETTY_FUNCTION__; }

	virtual void OnEvent(::Framework::Network::Session::Observer::E_EVENT event) {
		switch(event)
		{
		case ::Framework::Network::Session::Observer::E_EVENT::OPEN:
			OnOpen();
			break;
		case ::Framework::Network::Session::Observer::E_EVENT::CLOSE:
			OnClose();
			break;
		case ::Framework::Network::Session::Observer::E_EVENT::MESSAGE:
			OnMessage();
			break;
		}
	}

	virtual void OnConnectFail(boost::system::error_code& ec) {
		LOG(INFO) << __PRETTY_FUNCTION__ << ec.value();
		handle_->SetTimer(1000, 2, boost::bind(&client::OnTime, this, _1, _2));
	}

	std::shared_ptr<client> CAPTURE() { return shared_from_this(); }

	void Connect(std::string & port) {

		connect = std::chrono::system_clock::now();

		LOG(INFO) << __PRETTY_FUNCTION__;

		auto self(this->CAPTURE());

		port_ = port;
		session_ = ::Framework::Network::create_session_multithread();
		session_->set_observer(this->CAPTURE());
		session_->Connect(port_);

		nReadCount = 0;
	}

private:
	void OnTime(const boost::system::error_code & ec, int id)
	{
		LOG(INFO) << __PRETTY_FUNCTION__;
		if (1 == id)
		{
			Test::ping_syn syn;
			syn.set_id(1);
			//pingpong.set_uuid("abcdefg");
			session_->Send(syn);
		}
		else if(2 == id)
		{
			this->Connect(port_);
		}
	}
	void OnOpen() {

		LOG(INFO) << __PRETTY_FUNCTION__;

		connect_complete = std::chrono::system_clock::now();

		int gap = std::chrono::duration_cast<std::chrono::milliseconds>(connect_complete - connect).count();
		//if (gap >= 1000)
		{
			LOG(INFO) << __PRETTY_FUNCTION__ << "[DELAY CONNECT TIME : " << gap << "]";
		}
		//session_->Close();

		send = std::chrono::system_clock::now();
		Test::ping_syn syn;
		syn.set_id(1);
		session_->Send(syn);
	}

	void OnClose() {
		LOG(INFO) << __PRETTY_FUNCTION__;
		//auto self(shared_from_this());

		//auto self(this->CAPTURE());
		//session_ = NULL;

		handle_->SetTimer(1000, 2, boost::bind(&client::OnTime, this, _1, _2));
	}

//	void OnConnectFail(boost::system::error_code & ec) {
//		LOG(INFO) << __PRETTY_FUNCTION__ << ec.value();
//		handle_->SetTimer(1000, 2, boost::bind(&Client::OnTime, this, _1, _2));
//	}

	void OnMessage() {

		std::shared_ptr<::Framework::Network::Packet> packet = session_->clsReadPacket_deque_.front();
		session_->clsReadPacket_deque_.pop_front();

		LOG(INFO) << __PRETTY_FUNCTION__ << "client read command : " << packet->get_command() << "]";

		if (2 == packet->get_command())
		{
			read = std::chrono::system_clock::now();

			int gap = std::chrono::duration_cast<std::chrono::milliseconds>(read - send).count();
			//if (gap >= 1000)
			{
				LOG(INFO) << __PRETTY_FUNCTION__ << "[LETENCY TIME : " << gap << "]";
			}

			google::protobuf::io::ArrayInputStream is(packet->BodyBuffer(), packet->get_body_length_());
			Test::pong_ack ack;
			ack.ParseFromZeroCopyStream(&is);

			//int commands = ack.command();
			int commands = 0;
			int id = ack.id();

			LOG(INFO) << __PRETTY_FUNCTION__ << "[ client pong_ack id : " << id << "] [commands : " << commands << "]";
		}

		nReadCount ++;

		if (3 == nReadCount)
		{
			//handle_->SetTimer(1000, 2, boost::bind(&client::OnTime, this, _1, _2));
			session_->Close();
		}
		else
		{
			handle_->SetTimer(1000, 1, boost::bind(&client::OnTime, this, _1, _2));
		}


	}

private:
	std::shared_ptr<::Framework::Network::Session> session_;
	std::shared_ptr<::Framework::Timer::Handle> handle_;

	std::string port_;
	int nReadCount = 0;

	std::chrono::system_clock::time_point send;
	std::chrono::system_clock::time_point read;

	std::chrono::system_clock::time_point connect;
	std::chrono::system_clock::time_point connect_complete;
};

::Framework::Memory::Lock_Function_new_Pool<server> serverPool;
::Framework::Memory::Function_new_Pool<client> client_pool;


void OnAcceptEvent(std::shared_ptr<::Framework::Network::Session> session, unsigned short port)
{
	LOG(INFO) << __PRETTY_FUNCTION__;
	LOG(INFO) << __PRETTY_FUNCTION__ << "[port : " << port << "]";

	std::shared_ptr<server> server = serverPool.NEW();
	session->set_observer(server->CAPTURE());
	server->set_session(session->CAPTURE());
}



class test_framework_network_connection : public testing::Test
{
public:


public:

	int nCount = 1000;

	// Sets up the test fixture.
	virtual void SetUp() {

		{
			::Framework::Network::Listener * pListener = new ::Framework::Network::Listener;
			pListener->set_OnAcceptEvent_(std::bind(OnAcceptEvent, std::placeholders::_1, std::placeholders::_2));
			pListener->Listen(5000);
		}

		{
			::Framework::Network::Listener * pListener = new ::Framework::Network::Listener;
			pListener->set_OnAcceptEvent_(std::bind(OnAcceptEvent, std::placeholders::_1, std::placeholders::_2));
			pListener->Listen(5001);
		}

		//::Framework::Network::Static::Instance().set_OnAcceptEvent_(std::bind(OnAcceptEvent, std::placeholders::_1, std::placeholders::_2));
	}

	// Tears down the test fixture.
	virtual void TearDown() {}
};

TEST_F(test_framework_network_connection, test_framework_network_connection)
{
	for (int i = 0; i < nCount; i++)
	//for (int i = 0; i < 1; i++)
	{
		{
			std::shared_ptr<client> client = client_pool.NEW();
			std::string port("5000");
			client->Connect(port);
		}

		{
			std::shared_ptr<client> client = client_pool.NEW();
			std::string port("5001");
			client->Connect(port);
		}
	}
}


#endif /* TEST_FRAMEWORK_NETWORK_CONNECTION_H_ */
